package com.shop.aop;

import com.shop.annotation.RedisLock;
import com.shop.model.CustomAtom;
import com.shop.model.vo.Rsp;
import com.shop.service.RedisService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @BelongsProject: shop
 * @BelongsPackage: com.shop.aop
 * @Author: 尹标
 * @CreateTime: 2020-11-23 11:05
 * @Description: 分布锁切面类
 */
@Order(1)
@Aspect
@Component
public class DistributedSync {
    /**
     * 切入点表达式
     */
    @Pointcut("@annotation(redisLock)")
    public void SyncPointcut(RedisLock redisLock) {
    }

    /**
     * redis实现方法
     */
    @Resource
    private RedisService redisService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 切入点注解
     *
     * @param joinPoint
     * @return
     */
    @Around("SyncPointcut(redisLock)")
    public Object doAround(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        //获取注解中的参数
        String spel = redisLock.key();
        String lockName = redisLock.lockName();

        //加锁key为
        String redisKey = getRedisKey(joinPoint, lockName, spel);

        //过期时间为
        int expires = redisLock.expire();

        //锁值为
        String value = UUID.randomUUID().toString();

        System.out.println(redisKey + expires);
        //进行尝试上锁操作
        int count = 10;
        while (true) {
            boolean lock = redisService.getlock(redisKey, value);
            //尝试10次还不成功就放弃上锁
            if (count <= 0) {
                return Rsp.fail().msg("尝试上锁失败");
                // return Rsp.fail().msg("网络不太好呢,稍后再试");
            }
            //判断是否上锁成功
            if (!lock) {
                count--;
                Thread.sleep(100);
            } else {
                break;
            }
        }
        //设置过期时间
        System.out.println("加锁成功  键:" + redisKey + " 值:" + redisLock.expire());
        redisTemplate.expire(redisKey, redisLock.expire(), TimeUnit.SECONDS);

        Thread thread = Thread.currentThread();
        //设置自定义原子类初始化数据
        CustomAtom atom = new CustomAtom(true);
        //守护线程,看门狗
        Thread daemon = new Thread(() -> {
            //设置工作线程超过一定时间发送消息给开发者
            int expire = 10;
            while (true) {
                try {
                    //每隔一秒监测一次
                    Thread.sleep(1000);
                    if (--expire == 0) {
                        //这里发送消息给开发者
                        System.out.println("发消息给开发者");
                    }
                    if (atom.ok && thread.isAlive()) {
                        System.out.println("续期");
                        redisService.set(redisKey, value, 2l);
                    } else {
                        redisService.remove(redisKey);
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        daemon.setDaemon(true);
        daemon.start();
        Object result = null;
        try {
            //执行方法
            result = joinPoint.proceed();
        } finally {
            //解锁
            System.out.println("执行完毕解锁");
            atom.ok = false;
            redisService.unlock(redisKey, value);
        }
        atom.ok = false;
        return result;
    }

    /**
     * 支持 #p0 参数索引的表达式解析
     *
     * @param rootObject 根对象,method 所在的对象
     * @param spel       表达式
     * @param method     ，目标方法
     * @param args       方法入参
     * @return 解析后的字符串
     */
    public static String parse(Object rootObject, String spel, Method method, Object[] args) {
        if (StringUtils.isEmpty(spel)) {
            return null;
        }
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u =
                new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        if (StringUtils.isEmpty(paraNameArr)) {
            return spel;
        }
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject, method, args, u);
        //把方法参数放入SPEL上下文中
        // for (int i = 0; i < paraNameArr.length; i++) {
        //     context.setVariable(paraNameArr[i], args[i]);
        // }
        return parser.parseExpression(spel).getValue(context, String.class);
    }


    /**
     * 将spel表达式转换为字符串
     *
     * @param joinPoint 切点
     * @return redisKey
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint, String lockName, String spel) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Object target = joinPoint.getTarget();
        Object[] arguments = joinPoint.getArgs();
        String parse = parse(target, spel, targetMethod, arguments);
        return "REDIS_LOCK_PREFIX:" + lockName + ":" + parse;
    }
}
